Prototype of mochi kernel

[Mochi] is a functional language on top of Python 3. I was wondering how hard it woudl be to write an IPython wrapper kernel.

The basic wasn't tht hard, it requires a few patches to mochi, but works fine. You can find the wrapper kernel here

You can find the wrapper kernel here

Below is just the Project readme converted as a notebook

Mochi

Mochi is a dynamically typed programming language for functional programming and actor-style programming.

Its interpreter is written in Python3. The interpreter translates a program written in Mochi to Python3's AST / bytecode.

Features

  • Python-like syntax
  • Tail recursion optimization (self tail recursion only), and no loop syntax
  • Re-assignment are not allowed in function definition.
  • Basic collection type is a persistent data structure. (using Pyrsistent)
  • Pattern matching / Data types, like algebraic data types
  • Pipeline operator
  • Syntax sugar of anonymous function definition
  • Actor, like the actor of Erlang(using Eventlet)
  • Macro, like the traditional macro of Lisp
  • Builtin functions includes functions exported by itertools module, recipes, functools module and operator module

Examples

Factorial


In [1]:
def factorial(n, m):
    if n == 1:
        m
    else:
        factorial(n - 1, n * m)


<function factorial at 0x1069ec730>

In [2]:
factorial(100, 1)


93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

Or


In [3]:
def factorial:
    n: factorial(n, 1)
    0, acc: acc
    n, acc: factorial(n - 1, acc * n)


<function factorial at 0x106b7e730>

In [4]:
factorial(100)


93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

FizzBuzz


In [5]:
def fizzbuzz(n):
    match [n % 3, n % 5]:
        [0, 0]: "fizzbuzz"
        [0, _]: "fizz"
        [_, 0]: "buzz"
        _: n


<function fizzbuzz at 0x106336b70>

In [6]:
range(1, 31) |> map(fizzbuzz) |> pvector() |> print


pvector([1, 2, 'fizz', 4, 'buzz', 'fizz', 7, 8, 'fizz', 'buzz', 11, 'fizz', 13, 14, 'fizzbuzz', 16, 17, 'fizz', 19, 'buzz', 'fizz', 22, 23, 'fizz', 'buzz', 26, 'fizz', 28, 29, 'fizzbuzz'])
None

Actor


In [7]:
def show():
    receive:
        message:
            print(message)
            show()

actor = spawn(show)

send('foo', actor)
actor ! 'bar' # send('bar', actor)
sleep(1)


<function show at 0x106b7ebf8>
<mochi.actor.Actor object at 0x107485860>
None
None
foo
bar
None

In [8]:
1


1

In [9]:
'foo' !> spawn(show)

sleep(1)


None
foo
None

In [10]:
['foo', 'bar'] !&> spawn(show)


()

The meaning of the above is the same as the meaning of the following.


In [11]:
spawn(show) ! 'foo'
spawn(show) ! 'bar'
sleep(1)


foo
bar
None
None
foo
bar
None

Flask


In [14]:
from flask import Flask

app = Flask('demo')

@app.route('/')
def hello():
    'Hello World!'

app.run()



  File "/Users/bussonniermatthias/dev/mochi/mochi/mochi.py", line 3026, in eval_tokens
    exec(code, global_env)
  File "<string>", line 0, in <module>
*** ERROR: No module named 'flask'

aif


In [15]:
macro aif(test, true_expr, false_expr):
    quasi_quote:
        it = unquote(test)
        if it:
            unquote(true_expr)
        else:
            unquote(false_expr)


<function aif at 0x10deecd08>

In [16]:
aif([], first(it), "empty")


empty

In [17]:
aif([10, 20], first(it), "empty")


10

Requirements

  • CPython >= 3.2 or PyPy >= 3.2.1
  • rply >= 0.7.2
  • pyrsistent >= 0.6.3
  • pathlib >= 1.0.1
  • eventlet >= 0.15.2

Installation

$ pip3 install mochi

Usage

REPL

$ mochi
>>>

loading and running a file

$ cat kinako.mochi
print('kinako')
$ mochi kinako.mochi
kinako
$

byte compilation

$ mochi -c kinako.mochi > kinako.mochic

running a byte-compiled file

$ mochi -e kinako.mochic
kinako
$

Examples for each feature

Persistent data structures


In [18]:
[1, 2, 3]


pvector([1, 2, 3])

In [19]:
v(1, 2, 3)


pvector([1, 2, 3])

In [20]:
vec = [1, 2, 3]
vec2 = vec.set(0, 8)


pvector([1, 2, 3])
pvector([8, 2, 3])

In [21]:
vec


pvector([1, 2, 3])

In [22]:
[x, y, z] = vec
x # => 1
y # => 2
z # => 3

get(vec, 0) # => 1
get(vec, 0, 2) # => [1, 2]

{'x': 100, 'y': 200}


3
1
2
3
1
pvector([1, 2])
pmap({'x': 100, 'y': 200})

In [23]:
ma = {'x': 100, 'y': 200}
ma.get('x') # => 100
ma.x # => 100
ma2 = ma.set('x', 10000)


pmap({'x': 100, 'y': 200})
100
100
pmap({'x': 10000, 'y': 200})

In [25]:
ma # => pmap({'y': 200, 'x': 100})
get(ma, 'y') # => 200

m(x=100, y=200)


pmap({'x': 100, 'y': 200})
200
pmap({'x': 100, 'y': 200})

In [26]:
s(1, 2, 3)


pset([1, 2, 3])

In [27]:
b(1, 2, 3)


pbag([1, 2, 3])

Function definitions


In [29]:
def hoge(x):
    hoge + str(x)


<function hoge at 0x10deec840>

In [30]:
hoge(3)



  File "/Users/bussonniermatthias/dev/mochi/mochi/mochi.py", line 3026, in eval_tokens
    exec(code, global_env)
  File "<string>", line 1, in <module>
  File "<string>", line 2, in hoge
*** ERROR: unsupported operand type(s) for +: 'function' and 'str'

Pattern matching


In [31]:
lis = [1, 2, 3]

match lis:
    [1, 2, x]: x
    _: None


pvector([1, 2, 3])
3

In [32]:
match lis:
    [1, &rest]: rest
    _: None


pvector([2, 3])

In [33]:
foo_map = {'foo' : 'bar'}

match foo_map:
    {'foo' : value}: value
    _: None


pmap({'foo': 'bar'})
bar

In [34]:
match 10:
    int(x): 'int'
    float(x): 'float'
    str(x): 'str'
    bool(x): 'bool'
    _: 'other'


int

In [35]:
match [1, 2, 3]:
    [1, str(x), 3]: 'str'
    [1, int(x), 3]: 'int'
    _: 'other'


int

Records


In [36]:
record Mochi
record AnkoMochi(anko) < Mochi
record KinakoMochi(kinako) < Mochi

anko_mochi = AnkoMochi(anko=3)

isinstance(anko_mochi, Mochi)


<class '__main__.Mochi'>
<class '__main__.AnkoMochi'>
<class '__main__.KinakoMochi'>
AnkoMochi(anko=3)
True

In [37]:
isinstance(anko_mochi, AnkoMochi)


True

In [38]:
isinstance(anko_mochi, KinakoMochi)


False

In [39]:
match anko_mochi:
    KinakoMochi(kinako): 'kinako ' * kinako + ' mochi'
    AnkoMochi(anko): 'anko ' * anko + 'mochi'
    Mochi(_): 'mochi'


anko anko anko mochi

In [40]:
record Person(name, age):
    def show(self):
        print(self.name + ': ' + self.age)

foo = Person('foo', '32')
foo.show()


<class '__main__.Person'>
Person(name='foo', age='32')
foo: 32
None

Bindings


In [42]:
x = 3000


3000

In [43]:
[a, b] = [1, 2]
a


2
1

In [44]:
b


2

In [45]:
[c, &d] = [1, 2, 3]
c


pvector([2, 3])
1

In [46]:
d


pvector([2, 3])

Data types, like algebraic data types


In [41]:
data Point:
    Point2D(x, y)
    Point3D(x, y, z)

# The meaning of the above is the same as the meaning of the following.
# record Point
# record Point2D(x, y) < Point
# record Point3D(x, y, z) < Point

p1 = Point2D(x=1, y=2)


<class '__main__.Point3D'>
Point2D(x=1, y=2)

In [ ]:
p2 = Point2D(3, 4)

In [47]:
p1.x


1

Pattern-matching function definitions


In [48]:
data Point:
    Point2D(x, y)
    Point3D(x, y, z)

def offset:
    Point2D(x1, y1), Point2D(x2, y2):
        Point2D(x1 + x2, y1 + y2)
    Point3D(x1, y1, z1), Point3D(x2, y2, z2):
        Point3D(x1 + x2, y1 + y2, z1 + z2)
    _: None

offset(Point2D(1, 2), Point2D(3, 4))


<class '__main__.Point3D'>
<function offset at 0x10dff8840>
Point2D(x=4, y=6)

In [49]:
offset(Point3D(1, 2, 3), Point3D(4, 5, 6))


Point3D(x=5, y=7, z=9)

In [50]:
def show:
    int(x), message: print('int', x, message)
    float(x), message: print('float', x, message)
    _: None

show(1.0, 'msg')


<function show at 0x10e1951e0>
float 1.0 msg
None

Anonymous function

Arrow expression.


In [53]:
add = (x, y) -> x + y
add(1, 2)


<function _gs236 at 0x10d16abf8>
3

In [54]:
add = -> $1 + $2
add(1, 2)


<function _gs237 at 0x10d16ad90>
3

In [55]:
foo = (x, y) ->
    if x == 0:
        y
    else:
        x

foo(1, 2)


<function _gs238 at 0x10d16aae8>
1

In [56]:
foo(0, 2)


2

In [57]:
pvector(map(-> $1 * 2, [1, 2, 3]))


pvector([2, 4, 6])

Pipeline operator


In [58]:
add = -> $1 + $2
2 |> add(10) |> add(12)


<function _gs241 at 0x10d16ac80>
24

In [59]:
None |>? add(10) |>? add(12)


None

Lazy sequences


In [60]:
def fizzbuzz(n):
    match [n % 3, n % 5]:
        [0, 0]: "fizzbuzz"
        [0, _]: "fizz"
        [_, 0]: "buzz"
        _: n


result = range(1, 31) |> map(fizzbuzz)
pvector(result)


<function fizzbuzz at 0x10dff86a8>
<map object at 0x10e166c50>
pvector([1, 2, 'fizz', 4, 'buzz', 'fizz', 7, 8, 'fizz', 'buzz', 11, 'fizz', 13, 14, 'fizzbuzz', 16, 17, 'fizz', 19, 'buzz', 'fizz', 22, 23, 'fizz', 'buzz', 26, 'fizz', 28, 29, 'fizzbuzz'])

In [61]:
pvector(result)


pvector([])

In [62]:
pvector(result)


pvector([])

In [63]:
lazy_result = range(1, 31) |> map(fizzbuzz) |> lazyseq()
pvector(lazy_result)


lazyseq(<map object at 0x10e083278>)
pvector([1, 2, 'fizz', 4, 'buzz', 'fizz', 7, 8, 'fizz', 'buzz', 11, 'fizz', 13, 14, 'fizzbuzz', 16, 17, 'fizz', 19, 'buzz', 'fizz', 22, 23, 'fizz', 'buzz', 26, 'fizz', 28, 29, 'fizzbuzz'])

In [64]:
pvector(lazy_result)


pvector([1, 2, 'fizz', 4, 'buzz', 'fizz', 7, 8, 'fizz', 'buzz', 11, 'fizz', 13, 14, 'fizzbuzz', 16, 17, 'fizz', 19, 'buzz', 'fizz', 22, 23, 'fizz', 'buzz', 26, 'fizz', 28, 29, 'fizzbuzz'])

In [65]:
pvector(lazy_result)


pvector([1, 2, 'fizz', 4, 'buzz', 'fizz', 7, 8, 'fizz', 'buzz', 11, 'fizz', 13, 14, 'fizzbuzz', 16, 17, 'fizz', 19, 'buzz', 'fizz', 22, 23, 'fizz', 'buzz', 26, 'fizz', 28, 29, 'fizzbuzz'])

Macros


In [66]:
macro rest_if_first_is_true(first, &args):
     match first:
         quote(True): quasi_quote(v(unquote_splicing(args)))
         _: quote(False)

rest_if_first_is_true(True, 1, 2, 3)


<function rest_if_first_is_true at 0x10e195378>
pvector([1, 2, 3])

In [67]:
rest_if_first_is_true("foo", 1, 2, 3)


False

In [ ]:
macro pipeline(&args):
    [Symbol('|>')] + args

pipeline([1, 2, 3],
         map(-> $1 * 2),
         filter(-> $1 != 2),
         pvector())
# => pvector([4, 6])
```

### Including a file at compile time
```sh
$ cat anko.mochi
x = 10000
y = 20000
```

```python
require 'anko.mochi'
x
# => 10000

x = 30000

require 'anko.mochi' # include once at compile time
x
# => 30000
```

### Module
```python
module Math:
    export add, sub
    
    def add(x, y):
        x + y
    
    def sub(x, y):
        x - y

Math.add(1, 2)
# => 3
```

```sh
$ cat foobar.mochi
foo = 'foo'
bar = 'bar'
```

```python
require 'foobar.mochi'
[foo, bar]
# => pvector(['foo', 'bar'])

foo = 'foofoofoo'

module X:
    export foobar
    require 'foobar.mochi'
    def foobar:
        [foo, bar]

X.foobar()
# => pvector(['foo', 'bar'])

[foo, bar]
# => pvector(['foofoofoo', 'bar'])
```

## TODO
- Documentation
- Improvement of parsing
- Support class definition

## License
MIT License

## Author
[i2y] (https://github.com/i2y)